package br.com.hs.nfe.business;

import br.com.hs.nfe.AppUtil;
import br.com.hs.nfe.common.app.AppConfig;
import br.com.hs.nfe.common.app.AppContext;
import br.com.hs.nfe.common.enumeration.AppContextEnum;
import br.com.hs.nfe.common.enumeration.ServicoSefazEnum;
import br.com.hs.nfe.common.exception.HSCommonException;
import br.com.hs.nfe.common.to.CertificadoTO;
import br.com.hs.nfe.common.to.EmitenteTO;
import br.com.hs.nfe.common.to.ProxyTO;
import br.com.hs.nfe.common.util.StringHelper;
import br.com.hs.nfe.util.EstadoTO;
import br.com.hs.nfe.util.UtilHelper;
import br.com.hs.nfe.ws.cadastro.CadConsultaCadastro2Stub;
import br.com.hs.nfe.ws.cancelamento.NfeCancelamento2Stub;
import br.com.hs.nfe.ws.consulta.NfeConsulta2Stub;
import br.com.hs.nfe.ws.dpec.consulta.SCEConsultaRFBStub;
import br.com.hs.nfe.ws.dpec.envio.SCERecepcaoRFBStub;
import br.com.hs.nfe.ws.exception.*;
import br.com.hs.nfe.ws.inutilizacao.NfeInutilizacao2Stub;
import br.com.hs.nfe.ws.recepcao.NfeRecepcao2Stub;
import br.com.hs.nfe.ws.recepcao.retorno.NfeRetRecepcao2Stub;
import br.com.hs.nfe.ws.ssl.HSProtocolSocketFactory;
import br.com.hs.nfe.ws.status.NfeStatusServico2Stub;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.impl.builder.StAXBuilder;
import org.apache.axiom.om.impl.builder.StAXOMBuilder;
import java.io.ByteArrayInputStream;
import java.net.URL;
import java.rmi.RemoteException;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import org.apache.axis2.client.Options;
import org.apache.commons.httpclient.protocol.Protocol;

/**
 * Classe responsável pela consulta ao web service.
 * @author Ranlive Hrysyk
 */
public class WSBusiness {

    private final int TIMEOUT = 60000;
    private X509Certificate certificate;
    private PrivateKey privateKey;

    /**
     * Construtor da classe WSBusiness.
     */
    public WSBusiness() {
        System.setProperty("sun.security.ssl.allowUnsafeRenegotiation", "true");
    }

    /**
     * Obtém a resposta do serviço do web service.
     */
    public String servico(String dadosMsg, EstadoTO uf, ServicoSefazEnum servicoSefazEnum, CertificadoTO certificadoTO) throws HSConnectionClientException, HSConnectionServerException, HSTimeoutException, HSProxyException, HSSocketException, HSWebServiceException {
        this.certificate = certificadoTO.getX509Certificate();
        this.privateKey = certificadoTO.getPrivateKey();

        configurarProxy();
        configurarConexao();

        switch (servicoSefazEnum.ordinal()) {
            case 0:
                return recepcao(dadosMsg, uf);
            case 1:
                return retRecepcao(dadosMsg, uf);
            case 2:
                return cancelamento(dadosMsg, uf);
            case 3:
                return inutilizacao(dadosMsg, uf);
            case 4:
                return statusServico(dadosMsg, uf);
            case 5:
                return consulta(dadosMsg, uf);
            case 6:
                return recepcaoRFB(dadosMsg);
            case 7:
                return consultaRFB(dadosMsg);
            case 8:
                return consultaCadastro(dadosMsg, uf);
        }
        return null;
    }

    /**
     * Serviço de recepção da NF-e.
     */
    private String recepcao(String xml, EstadoTO uf) throws HSConnectionClientException, HSConnectionServerException, HSTimeoutException, HSProxyException, HSSocketException, HSWebServiceException {
        try {
            URL url = new URL(UtilHelper.getInstance().getURLByUF(uf.getSigla(), ServicoSefazEnum.Recepcao));

            NfeRecepcao2Stub.NfeCabecMsg cabec = new NfeRecepcao2Stub.NfeCabecMsg();
            NfeRecepcao2Stub.NfeCabecMsgE cabecE = new NfeRecepcao2Stub.NfeCabecMsgE();
            cabec.setCUF(getUFCabecalho());
            cabec.setVersaoDados(getVersaoDados(ServicoSefazEnum.Recepcao));
            cabecE.setNfeCabecMsg(cabec);

            NfeRecepcao2Stub.NfeDadosMsg dados = new NfeRecepcao2Stub.NfeDadosMsg();
            dados.setExtraElement(getXMLOMElement(xml));

            NfeRecepcao2Stub stub = new NfeRecepcao2Stub(url.toString());
            stub._getServiceClient().setOptions(getOptions(stub._getServiceClient().getOptions()));

            return stub.nfeRecepcaoLote2(dados, cabecE).getExtraElement().toString();
        } catch (RemoteException e) {
            lancarExceptionWS(e);
        } catch (Exception e) {
            throw new HSWebServiceException(e.getMessage(), e);
        }
        return null;
    }

    /**
     * Serviço de retorno da recepção da NF-e.
     */
    private String retRecepcao(String xml, EstadoTO uf) throws HSConnectionClientException, HSConnectionServerException, HSTimeoutException, HSProxyException, HSSocketException, HSWebServiceException {
        try {
            URL url = new URL(UtilHelper.getInstance().getURLByUF(uf.getSigla(), ServicoSefazEnum.RetRecepcao));

            NfeRetRecepcao2Stub.NfeCabecMsg cabec = new NfeRetRecepcao2Stub.NfeCabecMsg();
            NfeRetRecepcao2Stub.NfeCabecMsgE cabecE = new NfeRetRecepcao2Stub.NfeCabecMsgE();
            cabec.setCUF(getUFCabecalho());
            cabec.setVersaoDados(getVersaoDados(ServicoSefazEnum.RetRecepcao));
            cabecE.setNfeCabecMsg(cabec);

            NfeRetRecepcao2Stub.NfeDadosMsg dados = new NfeRetRecepcao2Stub.NfeDadosMsg();
            dados.setExtraElement(getXMLOMElement(xml));

            NfeRetRecepcao2Stub stub = new NfeRetRecepcao2Stub(url.toString());
            stub._getServiceClient().setOptions(getOptions(stub._getServiceClient().getOptions()));

            return stub.nfeRetRecepcao2(dados, cabecE).getExtraElement().toString();
        } catch (RemoteException e) {
            lancarExceptionWS(e);
        } catch (Exception e) {
            throw new HSWebServiceException(e.getMessage(), e);
        }
        return null;
    }

    /**
     * Serviço de cancelamento da NF-e.
     */
    private String cancelamento(String xml, EstadoTO uf) throws HSConnectionClientException, HSConnectionServerException, HSTimeoutException, HSProxyException, HSSocketException, HSWebServiceException {
        try {
            URL url = new URL(UtilHelper.getInstance().getURLByUF(uf.getSigla(), ServicoSefazEnum.Cancelamento));

            NfeCancelamento2Stub.NfeCabecMsg cabec = new NfeCancelamento2Stub.NfeCabecMsg();
            NfeCancelamento2Stub.NfeCabecMsgE cabecE = new NfeCancelamento2Stub.NfeCabecMsgE();
            cabec.setCUF(getUFCabecalho());
            cabec.setVersaoDados(getVersaoDados(ServicoSefazEnum.Cancelamento));
            cabecE.setNfeCabecMsg(cabec);

            NfeCancelamento2Stub.NfeDadosMsg dados = new NfeCancelamento2Stub.NfeDadosMsg();
            dados.setExtraElement(getXMLOMElement(xml));

            NfeCancelamento2Stub stub = new NfeCancelamento2Stub(url.toString());
            stub._getServiceClient().setOptions(getOptions(stub._getServiceClient().getOptions()));

            return stub.nfeCancelamentoNF2(dados, cabecE).getExtraElement().toString();
        } catch (RemoteException e) {
            lancarExceptionWS(e);
        } catch (Exception e) {
            throw new HSWebServiceException(e.getMessage(), e);
        }
        return null;
    }

    /**
     * Serviço de inutilização da NF-e.
     */
    private String inutilizacao(String xml, EstadoTO uf) throws HSConnectionClientException, HSConnectionServerException, HSTimeoutException, HSProxyException, HSSocketException, HSWebServiceException {
        try {
            URL url = new URL(UtilHelper.getInstance().getURLByUF(uf.getSigla(), ServicoSefazEnum.Inutilizacao));

            NfeInutilizacao2Stub.NfeCabecMsg cabec = new NfeInutilizacao2Stub.NfeCabecMsg();
            NfeInutilizacao2Stub.NfeCabecMsgE cabecE = new NfeInutilizacao2Stub.NfeCabecMsgE();
            cabec.setCUF(getUFCabecalho());
            cabec.setVersaoDados(getVersaoDados(ServicoSefazEnum.Cancelamento));
            cabecE.setNfeCabecMsg(cabec);

            NfeInutilizacao2Stub.NfeDadosMsg dados = new NfeInutilizacao2Stub.NfeDadosMsg();
            dados.setExtraElement(getXMLOMElement(xml));

            NfeInutilizacao2Stub stub = new NfeInutilizacao2Stub(url.toString());
            stub._getServiceClient().setOptions(getOptions(stub._getServiceClient().getOptions()));

            return stub.nfeInutilizacaoNF2(dados, cabecE).getExtraElement().toString();
        } catch (RemoteException e) {
            lancarExceptionWS(e);
        } catch (Exception e) {
            throw new HSWebServiceException(e.getMessage(), e);
        }
        return null;
    }

    /**
     * Serviço de consulta status do serviço da NF-e.
     */
    private String statusServico(String xml, EstadoTO uf) throws HSConnectionClientException, HSConnectionServerException, HSTimeoutException, HSProxyException, HSSocketException, HSWebServiceException {
        try {
            URL url = new URL(UtilHelper.getInstance().getURLByUF(uf.getSigla(), ServicoSefazEnum.StatusServico));

            NfeStatusServico2Stub.NfeCabecMsg cabec = new NfeStatusServico2Stub.NfeCabecMsg();
            NfeStatusServico2Stub.NfeCabecMsgE cabecE = new NfeStatusServico2Stub.NfeCabecMsgE();
            cabec.setCUF(getUFCabecalho());
            cabec.setVersaoDados(getVersaoDados(ServicoSefazEnum.StatusServico));
            cabecE.setNfeCabecMsg(cabec);

            NfeStatusServico2Stub.NfeDadosMsg dados = new NfeStatusServico2Stub.NfeDadosMsg();
            dados.setExtraElement(getXMLOMElement(xml));

            NfeStatusServico2Stub stub = new NfeStatusServico2Stub(url.toString());
            stub._getServiceClient().setOptions(getOptions(stub._getServiceClient().getOptions()));

            return stub.nfeStatusServicoNF2(dados, cabecE).getExtraElement().toString();
        } catch (RemoteException e) {
            lancarExceptionWS(e);
        } catch (Exception e) {
            throw new HSWebServiceException(e.getMessage(), e);
        }
        return null;
    }

    /**
     * Serviço de consulta da NF-e.
     */
    private String consulta(String xml, EstadoTO uf) throws HSConnectionClientException, HSConnectionServerException, HSTimeoutException, HSProxyException, HSSocketException, HSWebServiceException {
        try {
            URL url = new URL(UtilHelper.getInstance().getURLByUF(uf.getSigla(), ServicoSefazEnum.ConsultaNf));

            NfeConsulta2Stub.NfeCabecMsg cabec = new NfeConsulta2Stub.NfeCabecMsg();
            NfeConsulta2Stub.NfeCabecMsgE cabecE = new NfeConsulta2Stub.NfeCabecMsgE();
            cabec.setCUF(getUFCabecalho());
            cabec.setVersaoDados(getVersaoDados(ServicoSefazEnum.ConsultaNf));
            cabecE.setNfeCabecMsg(cabec);

            NfeConsulta2Stub.NfeDadosMsg dados = new NfeConsulta2Stub.NfeDadosMsg();
            dados.setExtraElement(getXMLOMElement(xml));

            NfeConsulta2Stub stub = new NfeConsulta2Stub(url.toString());
            stub._getServiceClient().setOptions(getOptions(stub._getServiceClient().getOptions()));

            return stub.nfeConsultaNF2(dados, cabecE).getExtraElement().toString();
        } catch (RemoteException e) {
            lancarExceptionWS(e);
        } catch (Exception e) {
            throw new HSWebServiceException(e.getMessage(), e);
        }
        return null;
    }

    /**
     * Serviço de recepção da DPEC.
     */
    private String recepcaoRFB(String xml) throws HSConnectionClientException, HSConnectionServerException, HSTimeoutException, HSProxyException, HSSocketException, HSWebServiceException {
        try {
            URL url = new URL(AppConfig.getInstance().getUrlDPECEnvio());

            SCERecepcaoRFBStub.SceCabecMsg cabec = new SCERecepcaoRFBStub.SceCabecMsg();
            SCERecepcaoRFBStub.SceCabecMsgE cabecE = new SCERecepcaoRFBStub.SceCabecMsgE();
            cabec.setVersaoDados(getVersaoDados(ServicoSefazEnum.EnvioDpec));
            cabecE.setSceCabecMsg(cabec);

            SCERecepcaoRFBStub.SceDadosMsg dados = new SCERecepcaoRFBStub.SceDadosMsg();
            dados.setExtraElement(getXMLOMElement(xml));

            SCERecepcaoRFBStub stub = new SCERecepcaoRFBStub(url.toString());
            stub._getServiceClient().setOptions(getOptions(stub._getServiceClient().getOptions()));

            return stub.sceRecepcaoDPEC(dados, cabecE).getExtraElement().toString();
        } catch (RemoteException e) {
            lancarExceptionWS(e);
        } catch (Exception e) {
            throw new HSWebServiceException(e.getMessage(), e);
        }
        return null;
    }

    /**
     * Serviço de consulta da DPEC.
     */
    private String consultaRFB(String xml) throws HSConnectionClientException, HSConnectionServerException, HSTimeoutException, HSProxyException, HSSocketException, HSWebServiceException {
        try {
            URL url = new URL(AppConfig.getInstance().getUrlDPECConsulta());

            SCEConsultaRFBStub.SceCabecMsg cabec = new SCEConsultaRFBStub.SceCabecMsg();
            SCEConsultaRFBStub.SceCabecMsgE cabecE = new SCEConsultaRFBStub.SceCabecMsgE();
            cabec.setVersaoDados(getVersaoDados(ServicoSefazEnum.ConsultaDpec));
            cabecE.setSceCabecMsg(cabec);

            SCEConsultaRFBStub.SceDadosMsg dados = new SCEConsultaRFBStub.SceDadosMsg();
            dados.setExtraElement(getXMLOMElement(xml));

            SCEConsultaRFBStub stub = new SCEConsultaRFBStub(url.toString());
            stub._getServiceClient().setOptions(getOptions(stub._getServiceClient().getOptions()));

            return stub.sceConsultaDPEC(dados, cabecE).getExtraElement().toString();
        } catch (RemoteException e) {
            lancarExceptionWS(e);
        } catch (Exception e) {
            throw new HSWebServiceException(e.getMessage(), e);
        }
        return null;
    }

    /**
     * Serviço de consulta cadastro da NF-e.
     */
    private String consultaCadastro(String xml, EstadoTO uf) throws HSConnectionClientException, HSConnectionServerException, HSTimeoutException, HSProxyException, HSSocketException, HSWebServiceException {
        try {
            URL url = new URL(UtilHelper.getInstance().getURLByUF(uf.getSigla(), ServicoSefazEnum.ConsultaCadastro));

            CadConsultaCadastro2Stub.NfeCabecMsg cabec = new CadConsultaCadastro2Stub.NfeCabecMsg();
            CadConsultaCadastro2Stub.NfeCabecMsgE cabecE = new CadConsultaCadastro2Stub.NfeCabecMsgE();
            cabec.setCUF(getUFCabecalho());
            cabec.setVersaoDados(getVersaoDados(ServicoSefazEnum.ConsultaCadastro));
            cabecE.setNfeCabecMsg(cabec);

            CadConsultaCadastro2Stub.NfeDadosMsg dados = new CadConsultaCadastro2Stub.NfeDadosMsg();
            dados.setExtraElement(getXMLOMElement(xml));

            CadConsultaCadastro2Stub stub = new CadConsultaCadastro2Stub(url.toString());
            stub._getServiceClient().setOptions(getOptions(stub._getServiceClient().getOptions()));

            return stub.consultaCadastro2(dados, cabecE).getExtraElement().toString();
        } catch (RemoteException e) {
            lancarExceptionWS(e);
        } catch (Exception e) {
            throw new HSWebServiceException(e.getMessage(), e);
        }
        return null;
    }

    /**
     * Obtém o UF do cabeçalho.
     */
    private String getUFCabecalho() {
        EmitenteTO emitente = (EmitenteTO) AppContext.getInstance().getTO(AppContextEnum.EMITENTE, EmitenteTO.class);
        return emitente.getEnderEmit().getUFEnum().getCUF();
    }

    /**
     * Obtém a versão do schema XML.
     */
    private String getVersaoDados(ServicoSefazEnum servicoSefazEnum) throws HSCommonException {
        switch (servicoSefazEnum.ordinal()) {
            case 0:
                return AppConfig.getInstance().getVersaoSchema();
            case 1:
                return AppConfig.getInstance().getVersaoSchemaConsRecibo();
            case 2:
                return AppConfig.getInstance().getVersaoSchemaCancelamento();
            case 3:
                return AppConfig.getInstance().getVersaoSchemaInutilizacao();
            case 4:
                return AppConfig.getInstance().getVersaoSchemaStatusServico();
            case 5:
                return AppConfig.getInstance().getVersaoSchemaConsultaSituacao();
            case 6:
                return AppConfig.getInstance().getVersaoSchemaDPEC();
            case 7:
                return AppConfig.getInstance().getVersaoSchemaDPEC();
            case 8:
                return AppConfig.getInstance().getVersaoSchemaConsultaCadastro();
        }
        return null;
    }

    /**
     * Configura o protocolo da conexão.
     */
    private void configurarConexao() {
        Protocol protocol = new Protocol("https", new HSProtocolSocketFactory(certificate, privateKey), 443);
        Protocol.registerProtocol("https", protocol);
    }

    /**
     * Configura o proxy.
     */
    private void configurarProxy() {
        ProxyTO proxyTO = AppUtil.getProxyTO();
        if (proxyTO != null) {
            String porta = proxyTO.getPorta();
            String endereco = proxyTO.getEndereco();
            String usuario = proxyTO.getUsuario();
            String senha = proxyTO.getSenha();
            if (!StringHelper.isBlankOrNull(endereco)) {
                System.setProperty("https.proxyHost", endereco);
                System.setProperty("https.proxyPort", porta);
            } else {
                System.setProperty("https.proxyHost", "");
                System.setProperty("https.proxyPort", "");
            }
            if (!StringHelper.isBlankOrNull(usuario)) {
                System.setProperty("https.proxyUser", usuario);
                System.setProperty("https.proxyPassword", senha);
            } else {
                System.setProperty("https.proxyUser", "");
                System.setProperty("https.proxyPassword", "");
            }
        }
    }

    /**
     * Obtém os opções.
     */
    private Options getOptions(Options op) {
        op.setTimeOutInMilliSeconds(TIMEOUT);
        return op;
    }

    /**
     * Obtém o OMElement da String.
     */
    private OMElement getXMLOMElement(String xmlString) throws Exception {
        ByteArrayInputStream xmlStream = new ByteArrayInputStream(xmlString.getBytes(("UTF-8")));
        StAXBuilder builder = new StAXOMBuilder(xmlStream);
        return builder.getDocumentElement();
    }

    /**
     * Lança exceções personalizadas para cada tipo de problema.
     */
    private void lancarExceptionWS(Exception e) throws HSConnectionClientException, HSConnectionServerException, HSTimeoutException, HSProxyException, HSSocketException, HSWebServiceException {
        if ((System.getProperty("https.proxyHost") == null) || (System.getProperty("https.proxyHost").equals("false"))) {
            if (e.getMessage().indexOf("UnknownHostException") != -1) {
                throw new HSConnectionClientException(e.getMessage(), e);
            }
            if (e.getMessage().indexOf("NoRouteToHostException") != -1) {
                throw new HSConnectionClientException(e.getMessage(), e);
            }
            if (e.getMessage().indexOf("ConnectException") != -1) {
                throw new HSConnectionServerException(e.getMessage(), e);
            }
            if (e.getMessage().indexOf("SocketTimeoutException") != -1) {
                throw new HSTimeoutException(e.getMessage(), e);
            }
            if (e.getMessage().indexOf("ConnectTimeoutException") != -1) {
                throw new HSTimeoutException(e.getMessage(), e);
            }
            if (e.getMessage().indexOf("IOException") != -1) {
                throw new HSProxyException(e.getMessage(), e);
            }
            if (e.getMessage().indexOf("SocketException") != -1) {
                throw new HSSocketException(e.getMessage(), e);
            }
            throw new HSWebServiceException(e.getMessage(), e);
        }
        if (e.getMessage().indexOf("UnknownHostException") != -1) {
            throw new HSConnectionClientException(e.getMessage(), e);
        }
        if (e.getMessage().indexOf("IOException") != -1) {
            throw new HSTimeoutException(e.getMessage(), e);
        }
        if (e.getMessage().indexOf("SocketTimeoutException") != -1) {
            throw new HSTimeoutException(e.getMessage(), e);
        }
        if (e.getMessage().indexOf("ConnectTimeoutException") != -1) {
            throw new HSTimeoutException(e.getMessage(), e);
        }
        if (e.getMessage().indexOf("SocketException") != -1) {
            throw new HSSocketException(e.getMessage(), e);
        }
        throw new HSWebServiceException(e.getMessage(), e);
    }
}
